﻿using System;
using System.Text.RegularExpressions;
using System.IO;
using SHDocVw;
using mshtml;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Text;
using System.Linq;
using System.Reflection;
namespace AutoPageHelper
{
    public delegate void IEEventHander(IHTMLDocument doc, string url);
    public class IEProxy : IDisposable
    {
        public static string IEAppName = "iexplore";
        public static string bindID = "window.bindId";  // 绑定标志
        public IEEventHander OnDocumentComplete;
        public IEEventHander OnClosed;
        public IEEventHander OnBeforeNavigate;
        private InternetExplorer _mainWin;
        //private List<InternetExplorer> _subWins;
        public InternetExplorer MainWindow
        {
            get { return _mainWin; }
        }

        public bool Record
        {
            set;
            get;
        }
        public bool CaptureNewWindow
        {
            set;
            get;
        }

        public IEProxy()
        {
            //_subWins = new List<InternetExplorer>();
        }
        ~IEProxy()
        {
            Dispose(false);
        }
        #region IDisposable 成员
        bool bDisposed = false;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (bDisposed)
                return;
            if (disposing)
            {
                if (_mainWin == null)
                    return;
                _mainWin.PutProperty(bindID, null); // 绑定标志
                _mainWin.DocumentComplete -= new DWebBrowserEvents2_DocumentCompleteEventHandler(IE_DocumentComplete);
                _mainWin.BeforeNavigate2 -= new DWebBrowserEvents2_BeforeNavigate2EventHandler(IE_BeforeNavigate2);
                _mainWin.OnQuit -= new DWebBrowserEvents2_OnQuitEventHandler(IE_OnQuit);
            }
            bDisposed = true;
        }
        #endregion

        private InternetExplorer GetIEWin(string urlPattern)
        {
            ShellWindows wins = new ShellWindows();
            Regex regUrl = new Regex(".+");
            if (!String.IsNullOrEmpty(urlPattern))
                regUrl = MyUtility.GetRegex(urlPattern);

            foreach (InternetExplorer win in wins)
            {
                string appName = Path.GetFileNameWithoutExtension(win.FullName);
                if (string.IsNullOrEmpty(appName)
                    || !appName.Equals(IEAppName, StringComparison.CurrentCultureIgnoreCase))
                    continue;

                var bid = win.GetProperty(bindID); // set null when disposing
                if (bid != null)
                {
                    int pid = Convert.ToInt32(bid);
                    Process prcs = Process.GetProcesses().FirstOrDefault(p => p.Id == pid);
                    if (prcs != null) // 仍在运行
                        continue;
                }

                if (regUrl.IsMatch(win.LocationURL))
                {
                    int pid = Process.GetCurrentProcess().Id;
                    win.PutProperty(bindID, pid); // 绑定标志
                    return win;
                }
            }
            return null;
        }
        public bool Bind(string captureUrl)
        {
            _mainWin = GetIEWin(captureUrl);
            if (_mainWin == null)
                return false;
            BindWin(_mainWin);
            object url = _mainWin.LocationURL;
            IE_DocumentComplete(_mainWin, ref url);

            return true;
        }

        protected void BindWin(InternetExplorer win)
        {
            if (win == null)
                return;
            win.DocumentComplete -= new DWebBrowserEvents2_DocumentCompleteEventHandler(IE_DocumentComplete);
            win.DocumentComplete += new DWebBrowserEvents2_DocumentCompleteEventHandler(IE_DocumentComplete);
            win.BeforeNavigate2 -= new DWebBrowserEvents2_BeforeNavigate2EventHandler(IE_BeforeNavigate2);
            win.BeforeNavigate2 += new DWebBrowserEvents2_BeforeNavigate2EventHandler(IE_BeforeNavigate2);
            win.OnQuit -= new DWebBrowserEvents2_OnQuitEventHandler(IE_OnQuit);
            if (win.Equals(_mainWin)) // only fired by main window
            {
                win.OnQuit += new DWebBrowserEvents2_OnQuitEventHandler(IE_OnQuit);
            }
            win.NewWindow3 -= new DWebBrowserEvents2_NewWindow3EventHandler(IE_NewWindow3);
            win.NewWindow3 += new DWebBrowserEvents2_NewWindow3EventHandler(IE_NewWindow3);
        }

        void IE_NewWindow3(ref object ppDisp, ref bool Cancel, uint dwFlags, string bstrUrlContext, string bstrUrl)
        {
            Debug.WriteLine("new window3:" + bstrUrl + "from:" + bstrUrlContext);
            if (Record)
            {
                string text = "NewWindow:{" + bstrUrlContext + ":=" + bstrUrl + "}";
                ActionRecorder.Default.SaveAction(text);
                Debug.WriteLine(text);
            }
            if (!CaptureNewWindow)
                return;
            InternetExplorerClass win = new InternetExplorerClass();
            ppDisp = win;
            BindWin(win);
        }


        void IE_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
        {
            InternetExplorer win = pDisp as InternetExplorer;
            if (win == null || string.IsNullOrEmpty(win.LocationURL))
                return;
            IHTMLDocument doc = win.Document as IHTMLDocument;
            IHTMLDocument2 doc2 = win.Document as IHTMLDocument2;
            string url;
            if (URL == null)
            {
                if (doc == null)
                    return;
                url = doc2.url;
            }
            else
            {
                url = (URL as string);
            }
            url = url.ToLower();
            string oldurl = doc2.url;
            if (Convert.ToInt32(Flags) == 64)
            {
                // top win???
            }
            
            if (Record)
            {
                string text = "BeforeNavigate:{" + oldurl + ":=" + url;
                if (PostData != null)
                {
                    Encoding enc = Encoding.GetEncoding((doc as IHTMLDocument2).defaultCharset);
                    //enc = Encoding.UTF8;
                    string postStr = (enc.GetString(PostData as byte[])).TrimEnd(new char[] { '\0' });
                    // 中文转换
                    postStr = UrlUtility.UrlDecode(postStr, enc); 
                    
                    text += " ?? " + postStr;
                }
                text += "}";
                ActionRecorder.Default.SaveAction(text);
                Debug.WriteLine(text);
            }

            
            if (OnBeforeNavigate != null)
            {
                OnBeforeNavigate(doc, url);
            }
        }

        void IE_OnQuit()
        {
            Dispose();
            if (OnClosed != null)
                OnClosed(null, null);
            Debug.WriteLine("Main window quit");
        }

        void IE_DocumentComplete(object pDisp, ref object URL)
        {
            InternetExplorer win = pDisp as InternetExplorer;
            if (win == null)
                return;
            IHTMLDocument doc = win.Document as IHTMLDocument;
            IHTMLDocument2 doc2 = win.Document as IHTMLDocument2;

            string url;
            if (URL == null)
            {
                if (doc == null)
                    return;
                url = doc2.url;
            }
            else
            {
                url = (URL as string);
            }
            url = url.ToLower();
            if (!(url.StartsWith("http://") 
                || url.StartsWith("https://") 
                || url.StartsWith("file://")))
            {
                // in AJAX, NOT EXACTLY fired
                Debug.WriteLine("Document complete(ajax):" + url);
                return;
            }
            else if (url != win.LocationURL.ToLower())
            {
                Debug.WriteLine("Document complete(iframe):" + url);
                // IFRAME 
            }
            else
            {
                Debug.WriteLine("Document complete:" + url);
                
            }
            if (!url.Equals(doc2.url, StringComparison.InvariantCultureIgnoreCase))
            {
                Debug.WriteLine("load error or redirect.");
                //return;
            }

            if (OnDocumentComplete != null)
                OnDocumentComplete(doc, url);

            if (Record)
            {
                //HTMLDocumentEvents2_Event evt.onkeypress += new HTMLDocumentEvents2_onkeypressEventHandler(IEDoc_onkeypress); // 无法响应用户操作
                if (string.IsNullOrEmpty(ActionRecorder.Default.RecordDirectory))
                {
                    string host = url.Replace("http://", "").Replace("file:///", "").Replace(":", "").Replace("\\", "/");
                    int i = host.IndexOf('/');
                    if (i > 0)
                    {
                        host = host.Substring(0, i);
                        ActionRecorder.Default.RecordDirectory = host;
                    }
                }
                ActionRecorder.Default.SavePage(doc);
                AttachEvents(doc as HTMLDocument);
            }
        }
#region DocEvents

        void AttachEvents(HTMLDocument doc)
        {
            // 挂接键盘鼠标事件
            DispHTMLDocument doc3 = doc as DispHTMLDocument;
            DHTMLEventHandler hdKeyboard = new DHTMLEventHandler(doc, doc3.onkeyup);
            hdKeyboard.Handler += new DHTMLEvent(IEDoc_onkeyup);
            doc3.attachEvent("onkeyup", hdKeyboard);
            //doc3.onkeyup = hdKeyboard;

            DHTMLEventHandler hdMouse = new DHTMLEventHandler(doc, doc3.onclick);
            hdMouse.Handler += new DHTMLEvent(IEDoc_onclick);
            doc3.attachEvent("onclick", hdMouse);
            //doc3.onclick = hdMouse;
            
            // ie8 only
            DHTMLEventHandler hdChangeProp = new DHTMLEventHandler(doc, doc3.onpropertychange);
            hdChangeProp += new DHTMLEvent(IEDoc_onpropertychange);
            doc3.attachEvent("onpropertychange", hdChangeProp);

            foreach (IHTMLFormElement frm in doc.forms)
            {
                DHTMLEventHandler hSumbmit = new DHTMLEventHandler(doc, frm.onsubmit);
                hSumbmit.Handler += new DHTMLEvent(IEFrm_submit);
                (frm as IHTMLElement2).attachEvent("onsubmit", hSumbmit);
                //frm.onsubmit = hSumbmit;
            }

            //TODO: 通过MutationEvent检测DOM变化，并重新整合事件
            // 挂接xmlhttprequest的 open 和 send 获取参数和地址，挂接onreadystatechange获取状态改变
            // 将下面脚本注入到 doc
            // 为每个时间创建一个隐藏的元素，事件设置如前所写onclick，在StateChange中触发该元素的click事件，进一步触发代码去doc中获取url
            // var ie_ajaxListener = new Object();
            // ie_ajaxListener.tempOpen = XMLHttpRequest.prototype.open;
            // XMLHttpRequest.prototype.open = function(method, url, aync) { ie_ajaxListener.tempOpen.apply(this, arguments); ajax_url.value = url; // save url }
            // ie_ajaxListener.tempStateChange = XMLHttpRequest.prototype.onreadystatechange;
            // XMLHttpRequest.prototype.onreadystatechange = function() { ie_ajaxListener.tempStateChange.apply(this, arguments);  }
//            IHTMLDOMNode body = doc.body as IHTMLDOMNode;

//            IHTMLInputElement input = doc.createElement("INPUT") as IHTMLInputElement;
//            input.type = "hidden";
//            input.name = "_input_ajax_";
//            (input as IHTMLElement).setAttribute("id", "_input_ajax_");
//            //(input as IHTMLElement).setAttribute("onchange", "_input_ajax_.fireEvent('onclick')"); // will trigger document_click
//            //(input as IHTMLElement).setAttribute("style", "display:none;");
            
//            body.appendChild(input as IHTMLDOMNode);

//            IHTMLScriptElement scriptElement = doc.createElement("script") as IHTMLScriptElement;
//            scriptElement.type = "text/javascript";
//            //scriptElement.defer = true;
//            scriptElement.text = @"var ie_ajaxListener = new Object();
//ie_ajaxListener.tempOpen = XMLHttpRequest.prototype.open;
//XMLHttpRequest.prototype.open = function(method, url, aync) {
//	if(!method) method = '';
//	if(!url) url = '';
//	if(!aync) aync = false;
//	ie_ajaxListener.tempOpen.apply(this, arguments); 
//	ie_ajaxListener.url = url; 
//	ie_ajaxListener.method = method;
//	ie_ajaxListener.aync = aync;
//	if (method.toLowerCase() == 'get' && url.indexOf('?') > 0)
//		ie_ajaxListener.data = url.split('?')[1];
//}
//ie_ajaxListener.tempSend = XMLHttpRequest.prototype.send;
//XMLHttpRequest.prototype.send = function(data) {
//	if (!data) data='';
//	ie_ajaxListener.tempSend.apply(this, arguments);
//	if(ie_ajaxListener.method.toLowerCase() == 'post')
//		s_ajaxListener.data = data;
//	if(this.onreadystatechange){
//		ie_ajaxListener.tempStateChange = this.onreadystatechange;
//		this.onreadystatechange = function() {
//			ie_ajaxListener.tempStateChange.apply(this, arguments);
//			if(this.readyState < 4 && this.readyState > 2) {
//				_input_ajax_.value = 'request: ' + ie_ajaxListener.url; 
//                _input_ajax_.fireEvent('onclick');
//			} else if(this.status == 200 || this.status == 304 || this.status == 0) {
//				_input_ajax_.value = 'complete: ' + ie_ajaxListener.url; 
//                _input_ajax_.fireEvent('onclick');
//			} else if(this.status >= 400) {
//				_input_ajax_.value = 'error: ' + ie_ajaxListener.url; 
//                _input_ajax_.fireEvent('onclick');
//			}
//		}
//	}
//}";
//            body.appendChild(scriptElement as IHTMLDOMNode);
        }

        bool IEFrm_submit(IHTMLEventObj pEvtObj, HTMLDocument doc)
        {
            if (pEvtObj == null)
                return false;
            string evts = null;
            if (!(pEvtObj.srcElement is IHTMLFormElement))
                return true;
            IHTMLFormElement src = pEvtObj.srcElement as IHTMLFormElement;
            string id = src.name;
            if (string.IsNullOrEmpty(id))
                id = src.action;
            evts = "submit:{" + id + ":=" + src.action + "}";
            Debug.WriteLine(evts);
            ActionRecorder.Default.Url = doc.url;
            ActionRecorder.Default.SaveAction(evts);
            return true;
        }

        bool IEDoc_onclick(IHTMLEventObj pEvtObj, HTMLDocument doc)
        {
            if (pEvtObj == null)
                return false;
            string id = "";
            string text = "";
            IHTMLElement src = pEvtObj.srcElement;
            if (src is IHTMLInputElement
                || src is IHTMLTextAreaElement
                || src is IHTMLSelectElement)
            {
                id = src.getAttribute("name") as string;
                if (string.IsNullOrEmpty(id))
                    id = src.getAttribute("id") as string;
                if (string.IsNullOrEmpty(id))
                    id = src.getAttribute("type") as string;
                text = src.getAttribute("value") as string;
                if (string.IsNullOrEmpty(text))
                    text = src.innerText;
            }
            else if (src is IHTMLAnchorElement
                || src is IHTMLAreaElement)
            {
                string href = src.getAttribute("href") as string;
                if (string.IsNullOrEmpty(href)
                    || href.StartsWith("javascript:", StringComparison.InvariantCultureIgnoreCase)
                    || href.StartsWith("#"))
                {
                    string clk = src.getAttribute("onclik") as string;
                    if (!string.IsNullOrEmpty(clk))
                        href = clk;
                }
                text = src.innerText;
                if (string.IsNullOrEmpty(text))
                    text = src.innerHTML;
                id = href;
            }
            else
            {
                id = src.getAttribute("id") as string;
                if (string.IsNullOrEmpty(id))
                {
                    string html = src.outerHTML;
                    int i = html.IndexOf('>');
                    if (i > 0)
                        html = html.Substring(0, i + 1);
                    id = html.Trim();
                }
                text = src.innerText;
                if (string.IsNullOrEmpty(text))
                    text = src.innerHTML;
                if (!string.IsNullOrEmpty(text) && text.Length > 30) // 30 chars at most
                    text = text.Substring(0, 30);
            }

            string evts = "click:{" + id + ":=" + text + "}";
            if (id.Equals("_input_ajax_"))
            {
                evts = "ajax:{" + text + "}";
            }
            
            ActionRecorder.Default.Url = doc.url;
            ActionRecorder.Default.SaveAction(evts);
            Debug.WriteLine(evts);
            return true;
        }
        bool IEDoc_onkeyup(IHTMLEventObj pEvtObj, HTMLDocument doc)
        {
            if (pEvtObj == null)
                return false;
            string evts = null;
            IHTMLElement src = pEvtObj.srcElement;
            if (src is IHTMLInputElement
                || src is IHTMLTextAreaElement
                || src is IHTMLSelectElement)
            {
                string id = src.getAttribute("name") as string;
                if (string.IsNullOrEmpty(id))
                    id = src.getAttribute("id") as string;
                string val = src.getAttribute("value") as string;
                if (string.IsNullOrEmpty(val))
                    val = src.innerText;
                evts = "keyup:{" + id + ":=" + val + "}";
            }
            ActionRecorder.Default.Url = doc.url;
            ActionRecorder.Default.SaveAction(evts);
            Debug.WriteLine(evts);
            return true;
        }
#endregion

    }
#region HTMLElementEvent

    /// Generic HTML DOM Event method handler.
    public delegate bool DHTMLEvent(IHTMLEventObj e, HTMLDocument d);
    /// Generic Event handler for HTML DOM objects.
    /// Handles a basic event object which receives an IHTMLEventObj which
    /// applies to all document events raised.
    [ComVisible(true)]
    public class DHTMLEventHandler // must pulic
    {
        public DHTMLEvent Handler;
        object originHandler;
        HTMLDocument Document;
        public DHTMLEventHandler(HTMLDocument doc, object existingHandler)
        {
            this.Document = doc;
            this.originHandler = existingHandler;
        }

        [DispId(0)]
        public void Call()
        {
            //parentWindow must in STA thread
            Thread callThread = new Thread(new ThreadStart(() => {
                if (originHandler != null && originHandler.GetType() != typeof(DBNull))
                    originHandler.GetType().InvokeMember("[DispID=0]", BindingFlags.InvokeMethod, null, originHandler, null);

                Handler(Document.parentWindow.@event, Document); 
            }));
            callThread.SetApartmentState(ApartmentState.STA);
            callThread.IsBackground = true;
            callThread.Start();
            callThread.Join();
        }
#endregion
    }
}
